Перейти к основному содержимому

5.03. Коллекции в Java

Разработчику Архитектору

Коллекции в Java

Коллекции в Java – сложный инструмент для работы с данными, который позволяет хранить, обрабатывать и манипулировать группами объектов. Collection — это интерфейс в java.util, который задаёт общий контракт для всех коллекций, хранящих группы объектов. Через него определяются ключевые операции: добавление и удаление элементов, проверка их наличия, очистка коллекции, получение размера и итерация.

Основные реализации — List, Set и Queue.

Сам интерфейс не потокобезопасен, но его можно обернуть в синхронизированную версию через Collections.synchronizedCollection() или использовать классы из java.util.concurrent. Collection работает с дженериками, что обеспечивает типобезопасность и упрощает работу с элементами.

Важно помнить, что Map не наследуется от Collection, так как представляет пары ключ–значение и имеет свою иерархию.

Collection – корневой интерфейс, представляющий собой группу элементов (объектов). Он определяет базовые операции над набором данных: добавление, удаление, проверка наличия, размер и т.д.

Данных бывает много, и в памяти их как-то надо структурировать.

Упорядочить – вывод списка пользователей в том порядке, в котором они регистрировались (ArrayList, LinkedHashMap).

Исключать дубликаты – например, загрузка данных из разных источников и удаление повторяющихся записей (HashSet, LinkedHashSet).

Сортировать – рейтинг игроков, сортировка товаров по цене (TreeSet, TreeMap).

Хранить пары «ключ-значение» - кэширование результатов вычислений, например (HashMap, TreeMap).

Работать с очередями и приоритетами – обработка задач в фоновых потоках (PriorityQueue, Deque).

Коллекции активно используются в веб-приложениях, бизнес-логике (валидация, фильтрация), алгоритмах (поиск, сортировка), параллелизме (concurrent коллекции).

У этого интерфейса есть основные подынтерфейсы:

  • List – упорядоченная коллекция с дубликатами;
  • Set – неупорядоченная колелкция без дубликатов;
  • Queue – очередь, используется для обработки элементов по принципу FIFO (First In, First Out, первый зашел - первый вышел);
  • Deque – двусторонная очередь.
  • Map не является частью иерархии Collection, но часто рассматривается вместе с ними.

List – упорядоченные списки. Реализует интерфейс List<E>. Поддерживает индексацию и дублирование элементов. Существуют реализации через ArrayList, LinkedList, Vector.

ArrayList – реализован на основе динамического массива (Array – это массив). Имеет быстрый доступ по индексу, медленную вставку/удаление в середине. Пример:

List<String> list = new ArrayList<>();
list.add("Java");

LinkedList – реализован как двусвязный список. Быстрая вставка/удаление, медленный доступ по индексу. Пример:

List<Integer> numbers = new LinkedList<>();
numbers.add(10);

Vector (устаревший) – похож на ArrayList, но синхронизированный, медленнее из-за блокировок. В современных приложениях вместо Vector используют Collections.synchronizedList() или CopyOnWriteArrayList. Пример:

List<String> legacyList = new Vector<>();

Set – набор уникальных элементов. Интерфейс Set<E> гарантирует отсутствие дубликатов. Существуют реализации через HashSet, LinkedHashSet, TreeSet.

HashSet хранит элементы в неопределённом порядке, использует хеш-таблицу, обладает быстрой вставкой, поиском и удалением. Пример:

Set<String> set = new HashSet<>();
set.add("Apple");

LinkedHashSet сохраняет порядок вставки элементов. Это комбинация HashSet и связанного списка. Пример:

Set<String> orderedSet = new LinkedHashSet<>();
orderedSet.add("First");

TreeSet хранит элементы в отсортированном порядке, реализован на основе красно-чёрного дерева. Пример:

Set<Integer> sortedSet = new TreeSet<>();
sortedSet.add(5);

Map – словарь (ключ и значение). Ключи уникальны.

HashMap – самая популярная реализация Map, позволяет один null в качестве ключа и несколько null в значениях, однако не гарантирует порядка.

Пример:

Map<String, Integer> map = new HashMap<>();
map.put("one", 1);

TreeMap – реализован на основе красно-чёрного дерева. Хранит пары в отсортированном по ключам порядке. Пример:

Map<String, Integer> sortedMap = new TreeMap<>();
sortedMap.put("banana", 2);

Hashtable – устаревшая, синхронизированная версия HashMap. Не позволяет использовать null в ключах и значениях. Вместо Hashtable лучше использовать ConcurrentHashMap или Collections.synchronizedMap(). Пример:

Map<String, String> legacyMap = new Hashtable<>();
legacyMap.put("key", "value");

LinkedHashMap сохраняет порядок вставки элементов и полезен, когда нужно сохранять историю изменений. Пример:

Map<String, String> history = new LinkedHashMap<>();
history.put("first", "start");

Queue – очереди. Они предназначены для обработки элементов по принципу FIFO (First In – First Out). Бывают как PriorityQueue или Deque. PriorityQueue – элементы извлекаются в порядке приоритета (по значению или с помощью компаратора). Не поддерживает null. Пример:

Queue<Integer> queue = new PriorityQueue<>();
queue.offer(3);
queue.poll(); // 3 (если минимальный)

Deque (Double Ended Queue) – можно добавлять или удалять элементы с обоих концов. Реализуется через ArrayDeque или LinkedList. Пример:

Deque<String> deque = new ArrayDeque<>();
deque.push("first");
deque.pop();

Давайте теперь разберём, когда что использовать.

ТипКогда использоватьПример
ArrayListНужен быстрый доступ по индексуСписок пользователей, список товаров в корзине
LinkedListЧастые вставки или удаления в начале или серединеРеализация очереди, стека, история действий (undo/redo)
HashSetНужны уникальные элементы, порядок не важенПроверка уникальности email, фильтрация дубликатов из базы данных
LinkedHashSetНужна уникальность и сохранение порядка вставкиИстория посещённых страниц, удаление дубликатов с сохранением порядка
TreeSetНужен отсортированный наборРейтинг игроков, очередь задач с приоритетом
HashMapОбычный словарь, скорость важнее порядкаКэширование объектов по ключу, хранение параметров конфигурации
LinkedHashMapНужен порядок вставкиЛог-файл с записями в порядке добавления, кэш LRU (Least Recently Used)
TreeMapНужен отсортированный словарьРейтинг студентов, поиск значений в диапазоне (например, дат)
PriorityQueueНужна очередь с приоритетомСистема обслуживания в банке, планировщик задач
Vector / HashtableУстаревшие, лучше избегать в новых проектахПоддержка устаревшего кода, многопоточные приложения без современных альтернатив

Все коллекции имеют общие методы, но не все реализации поддерживают все операции. Давайте соберём важнейшие:

  • add(E e) добавляет элемент в коллекцию, используется в List, Set, Queue, для Set игнорирует дубликаты.
  • remove(Object o) удаляет указанный элемент, используется во всех коллекциях, возвращает true, если элемент был найден.
  • contains(Object o) проверяет наличие элемента, использует equals() и hashCode().
  • containsKey() и containsValue() проверет наличие элемента для Map;
  • get(int index) получает элемент по индексу, используется только в List, причем в ArrayList – делает быстро, в LinkedList – медленно.
  • put(K key, V value) добавляет пару ключ-значение, используется в Map, перезаписывает значение, если ключ уже есть.
  • get(Object key) получает значение по ключу, используется в Map, возвращает null, если ключ не найден.
  • poll() извлекает и удаляет первый элемент, используется в Queue, Deque, возвращает null, если очередь пуста.
  • offer(E e) добавляет элемент в очередь, используется в Queue, возвращает false, если не удалось добавить.
  • peek() возвращает, но не удаляет первый элемент, используется в Queue, полезно для проверки перед извлечением.

Примеры:

// ArrayList
List<String> list = new ArrayList<>();
list.add("Java");
list.add(0, "Hello"); // вставка в начало
System.out.println(list.get(1)); // Java

// HashSet
Set<Integer> uniqueNumbers = new HashSet<>();
uniqueNumbers.add(1);
uniqueNumbers.add(1); // дубликат игнорируется
System.out.println(uniqueNumbers.contains(1)); // true

// HashMap
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 90);
System.out.println(scores.get("Alice")); // 90

// PriorityQueue
Queue<Integer> queue = new PriorityQueue<>();
queue.offer(5);
queue.offer(3);
System.out.println(queue.poll()); // 3 (минимальный элемент)